﻿using UnityEngine;
using System.Collections;
using System.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using SimpleJSON;

/* /////////////////////////////////////////////////////////////////////////////////////////////////
https://www.patreon.com/PluginIdea

Licensed under CC BY-SA after EarlyAccess ended. (see https://creativecommons.org/licenses/by-sa/4.0/)

///////////////////////////////////////////////////////////////////////////////////////////////// */

namespace PluginIdea
{

    class SpectruamFilterInfo
    {
        public float low;
        public float high;
    }

    class SendData
    {
        public float[] samples;
    }

    public class SystemAudioAction : MVRScript
    {
        private JSONStorableString floatTriggerActionsStr;

        private JSONStorableFloat sampleMultNum;
        private JSONStorableFloat rollingMaxSample;
        private FloatTrigger SystemRealTimeAudioAmpTrigger;
        private JSONStorableStringChooser lipSyncTriggerTypes;
        private JSONStorableStringChooser lipSyncResponePresets;

        private JSONStorableFloat responseFreqLow;
        private JSONStorableFloat responseFreqHigh;
        private JSONStorableFloat minLipAmount;
        private JSONStorableFloat maxLipAmount;
        private JSONStorableFloat updateSpeed;

        private List<string> lipSyncTriggerTypesList = new List<string> { "SimpleAudioAmp" };
        private List<string> lipResponsePresetsList = new List<string> { "All", "Man", "Girl", "Child" };
        private Dictionary<string, SpectruamFilterInfo> dicSpectTruamFilters = new Dictionary<string, SpectruamFilterInfo>();

        private float triggerTimer;
        private float triggerDur = 0.025f;

        private DAZMorph mouthOpen_morph;
        private float currentAmp;
        private float currentTargetMorphValue;
        private float currentMorphValue;

        public override void Init()
        {
            base.Init();

            if (containingAtom.type == "SessionPluginManager")
            {
                SuperController.LogError("please don't add on sessionPlunManager, parameter may can't be saved!");
                return;
            }

            DataInit();
            UIInit();
            InitEvent();
            MouseOpenMorphyInit();
        }

        private void DataInit()
        {
            dicSpectTruamFilters.Add("All", new SpectruamFilterInfo() { low = 0, high = 1 });
            dicSpectTruamFilters.Add("Man", new SpectruamFilterInfo() { low = 0f, high = 0.4f });
            dicSpectTruamFilters.Add("Girl", new SpectruamFilterInfo() { low = 0f, high = 0.55f });
            dicSpectTruamFilters.Add("Child", new SpectruamFilterInfo() { low = 0.3f, high = 0.45f });
        }

        private void InitEvent()
        {
            SuperController.singleton.onBeforeSceneSaveHandlers += OnBeforeSceneSave;
        }

        private void OnBeforeSceneSave()
        {
            floatTriggerActionsStr.val = SystemRealTimeAudioAmpTrigger.GetJSON().ToString();
        }

        public override void RestoreFromJSON(JSONClass jc, bool restorePhysical = true, bool restoreAppearance = true, JSONArray presetAtoms = null, bool setMissingToDefault = true)
        {
            base.RestoreFromJSON(jc, restorePhysical, restoreAppearance, presetAtoms, setMissingToDefault);

            JSONClass jcRestore = JSONClass.Parse(floatTriggerActionsStr.val).AsObject;
            SystemRealTimeAudioAmpTrigger.RestoreFromJSON(jcRestore);
        }

        private void UIInit()
        {
            //trigger
            SimpleTriggerHandler.LoadAssets();

            lipSyncResponePresets = Utils.SetupStringChooser(this, "ResponsePresets", lipResponsePresetsList, 2, false);
            lipSyncResponePresets.setCallbackFunction = (val) => {
                responseFreqLow.val = dicSpectTruamFilters[val].low;
                responseFreqHigh.val = dicSpectTruamFilters[val].high;
            };
            responseFreqLow = Utils.SetupSliderFloat(this, "ResponseFreqFilterLow", 0f, 0f, 1f, false);
            responseFreqLow.setCallbackFunction = (val) => {
                if (val >= responseFreqHigh.val - 0.1f)
                {
                    responseFreqLow.val = responseFreqHigh.val - 0.1f;
                }
            };

            responseFreqHigh = Utils.SetupSliderFloat(this, "ResponseFreqFilterHigh", 0.55f, 0f, 1f, false);
            responseFreqHigh.setCallbackFunction = (val) => {
                if (val <= responseFreqLow.val + 0.1f)
                {
                    responseFreqHigh.val = responseFreqLow.val + 0.1f;
                }
            };

            Utils.SetupSpacer(this, 50, false);

            floatTriggerActionsStr = Utils.SetupStorableString(this, "SystemAudioSourceInput", "");

            SystemRealTimeAudioAmpTrigger = new FloatTrigger(this, "SystemRealTimeAudioAmpTrigger");
            Utils.SetupButton(this, "Set Audio Action", SystemRealTimeAudioAmpTrigger.OpenPanel, false);

            sampleMultNum = Utils.SetupSliderFloat(this, "SampleMultNum", 2.8f, 0.3f, 5f, false);
            rollingMaxSample = Utils.SetupSliderFloat(this, "rollingMaxSample", 1f, 1f, 5f, false);

            Utils.SetupSpacer(this, 30, false);

            lipSyncTriggerTypes = Utils.SetupStringChooser(this, "LipSyncType", lipSyncTriggerTypesList, 0, false);

            minLipAmount = Utils.SetupSliderFloat(this, "MinLipAmount", 0.15f, -0.5f, 2f, false);
            maxLipAmount = Utils.SetupSliderFloat(this, "MaxLipAmount", 1.1f, -0.5f, 2f, false);
            updateSpeed = Utils.SetupSliderFloat(this, "UpdateSpeed", 100, 0, 300, false);

            Utils.SetupInfoText(this, "<b>SystemAudioAction 1.2</b> \n\n" +
                "<b>Note:If you want to use a simple audio response, it is recommended that you use the plug-in as a scenario plug-in (you can also add it to any Atom). " +
                "If you want to apply the header audio effect, please hang the plug -in on Person, otherwise it will not work properly.\n\n</b>" +
                "<b>ResponsePresets:</b>Some presets for sound channel filtering.\n\n" +
                "<b>ResponseFreqFilterLow:</b>Sound low frequency limit.\n\n" +
                "<b>ResponseFreqFilterHigh:</b>Sound high frequency limit\n\n" +
                "<b>Set Audio Action:</b> set the application target of audio response.\n\n" +
                "<b>SampleMultNum:</b> The multiplier of audio effect, the larger the multiplier, the stronger the effect.\n\n" +
                "<b>RollingMaxSample:</b> the maximum number of audio samples skipped each time. The larger the setting, the later the audio response.\n\n" +
                "<b>MinLipAmount:</b> the minimum opening of the mouth.\n\n" +
                "<b>MaxLipAmount:</b> the maximum opening of the mouth.\n\n" +
                "<b>UpdateSpeed:</b> mouth response speed.", 1000, true);
        }

        private void MouseOpenMorphyInit()
        {
            //get simple mouthopen morph
            SuperController.singleton.PauseSimulation(3, "Loading " + this.containingAtom.name);
            JSONStorable storableByID = this.containingAtom.GetStorableByID("geometry");
            if (storableByID != null)
            {
                DAZCharacterSelector dazcharacterSelector = storableByID as DAZCharacterSelector;
                GenerateDAZMorphsControlUI morphsControlUI = dazcharacterSelector.morphsControlUI;
                if (morphsControlUI != null)
                {
                    List<DAZMorph> morphs = morphsControlUI.GetMorphs();
                    this.mouthOpen_morph = morphs.FirstOrDefault(x => x.region == "Pose Controls/Head/Mouth" && x.morphName == "PHMMouthOpen");
                    if (this.mouthOpen_morph != null)
                    {
                        this.mouthOpen_morph.SetValue(0);
                        this.mouthOpen_morph.isTransient = false;
                        this.mouthOpen_morph.isRuntime = false;
                    }
                    else
                    {
                        SuperController.LogMessage("mouth open morph not found");
                    }
                }
            }
        }

        private void Update()
        {
            triggerTimer += Time.deltaTime;
            if (triggerTimer > triggerDur)
            {
                triggerTimer = 0;

                try
                {
                    if (UDPReceive.Instance.dataReveived != null)
                    {
                        string sendDataStr = Encoding.Default.GetString(UDPReceive.Instance.dataReveived);
                        SendData sendData = JsonUtility.FromJson<SendData>(sendDataStr);

                        currentAmp = GetAmp(sendData.samples) * 5;
                        currentTargetMorphValue = currentAmp - 0.5f;
                        currentTargetMorphValue = Mathf.Clamp(currentTargetMorphValue, minLipAmount.val, maxLipAmount.val);

                        SystemRealTimeAudioAmpTrigger.Trigger(currentAmp);
                    }
                }
                catch (System.Exception e)
                {
                    SuperController.LogError(e.Message);
                    return;
                }
            }

            currentMorphValue = Mathf.Lerp(currentMorphValue, currentTargetMorphValue, updateSpeed.val * Time.deltaTime);
        }
        private float GetAmp(float[] samples)
        {

            float ampValue = 0f;

            int low = (int)(responseFreqLow.val * samples.Length);
            int high = (int)(responseFreqHigh.val * samples.Length);
            for (int j = low; j < high; j++)
            {
                samples[j] /= rollingMaxSample.val + 1E-06f;
                samples[j] *= sampleMultNum.val;
                ampValue += samples[j];
            }
            ampValue /= (float)(high - low);
            return ampValue;
        }


        private void FixedUpdate()
        {
            this.mouthOpen_morph.SetValue(currentMorphValue);
        }

        private void OnDestroy()
        {
            UDPReceive.Instance.IsAlive = false;
            SuperController.singleton.onBeforeSceneSaveHandlers -= OnBeforeSceneSave;
        }
    }
}

